<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>室内运动轨迹跟踪 V22</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        h1 {
            text-align: center;
            color: #333;
        }
        .controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            margin-bottom: 20px;
            gap: 10px;
        }
        button {
            padding: 10px 15px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover {
            background-color: #45a049;
        }
        .status {
            margin-left: 20px;
            line-height: 40px;
        }
        .canvas-container {
            width: 100%;
            height: 60vh;
            border: 1px solid #ddd;
            border-radius: 4px;
            background-color: white;
            overflow: hidden;
            position: relative;
        }
        canvas {
            display: block;
            width: 100%;
            height: 100%;
        }
        .info {
            margin-top: 20px;
            background-color: #e9e9e9;
            padding: 10px;
            border-radius: 4px;
        }
        .debug-info {
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #ccc;
        }
        .calibration-controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            margin-top: 10px;
            gap: 5px;
        }
        .calibration-button {
            background-color: #2196F3;
            padding: 5px 10px;
            font-size: 14px;
        }
        .calibration-button:hover {
            background-color: #0b7dda;
        }
        .slider {
            width: 200px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>室内运动轨迹跟踪 V22</h1>
        <div class="controls">
            <button id="startBtn">开始跟踪</button>
            <button id="resetBtn">重置</button>
            <div class="status">状态: <span id="statusText">准备就绪</span></div>
        </div>
        <div class="canvas-container">
            <canvas id="trackCanvas"></canvas>
        </div>
        <div class="calibration-controls">
            <input type="range" id="stepLengthSlider" class="slider" min="5" max="50" value="20">
            <label for="stepLengthSlider">步长: <span id="stepLength">20</span></label>
            <button id="toggleCorrectionBtn" class="calibration-button">切换轨迹校正</button>
        </div>
        <div class="info">
            <div>加速度: <span id="acceleration">X: 0, Y: 0, Z: 0</span></div>
            <div>方向: <span id="orientation">α: 0°</span></div>
            <div>当前位置: <span id="position">X: 0, Y: 0</span></div>
            <div>步数: <span id="stepCount">0</span></div>
            <div>轨迹校正: <span id="correction">开启</span></div>
            <div>状态: <span id="walkingStatus">静止</span></div>
        </div>
    </div>
    <script>
        class KalmanFilter {
            constructor() {
                this.q = 0.1; // 过程噪声
                this.r = 0.1; // 测量噪声
                this.x = 0;   // 估计值
                this.p = 1;   // 估计误差
                this.k = 0;   // 卡尔曼增益
            }

            update(measurement) {
                this.p += this.q;
                this.k = this.p / (this.p + this.r);
                this.x += this.k * (measurement - this.x);
                this.p *= (1 - this.k);
                return this.x;
            }
        }

        document.addEventListener('DOMContentLoaded', function() {
            // 获取DOM元素
            const canvas = document.getElementById('trackCanvas');
            const ctx = canvas.getContext('2d');
            const startBtn = document.getElementById('startBtn');
            const resetBtn = document.getElementById('resetBtn');
            const stepLengthSlider = document.getElementById('stepLengthSlider');
            const toggleCorrectionBtn = document.getElementById('toggleCorrectionBtn');
            const statusText = document.getElementById('statusText');
            const accelerationEl = document.getElementById('acceleration');
            const orientationEl = document.getElementById('orientation');
            const positionEl = document.getElementById('position');
            const stepCountEl = document.getElementById('stepCount');
            const stepLengthEl = document.getElementById('stepLength');
            const correctionEl = document.getElementById('correction');
            const walkingStatusEl = document.getElementById('walkingStatus');
            
            // 设置画布大小
            function resizeCanvas() {
                const container = canvas.parentElement;
                canvas.width = container.clientWidth;
                canvas.height = container.clientHeight;
                render();
            }
            
            // 初始化变量
            let isTracking = false;
            let startX = 0;
            let startY = 0;
            let currentX = 0;
            let currentY = 0;
            let initialHeading = null;
            let stepCount = 0;
            let stepLength = 20;
            let pathHistory = [];
            let correctionEnabled = true;
            let isWalking = false;
            let walkingTimeout = null;
            
            // 传感器数据
            let accelData = { x: 0, y: 0, z: 0 };
            let orientationData = { alpha: 0, beta: 0, gamma: 0 };
            let accelSamples = []; // 存储加速度样本用于分析
            
            // 步行检测 - 改进的参数
            let lastAccelMagnitude = 0;
            let lastPeakTime = 0;
            const peakThreshold = 2.0; // 进一步提高阈值，减少误检测
            const minTimeBetweenSteps = 300; // 毫秒
            const minVariationForStep = 0.8; // 增加最小加速度变化
            
            // 初始化卡尔曼滤波器
            const kalmanX = new KalmanFilter();
            const kalmanY = new KalmanFilter();
            const kalmanZ = new KalmanFilter();
            
            // 初始化
            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);
            
            // 绑定按钮事件
            startBtn.addEventListener('click', toggleTracking);
            resetBtn.addEventListener('click', resetTracking);
            stepLengthSlider.addEventListener('input', () => {
                stepLength = parseInt(stepLengthSlider.value, 10);
                stepLengthEl.textContent = stepLength;
            });
            toggleCorrectionBtn.addEventListener('click', () => {
                correctionEnabled = !correctionEnabled;
                correctionEl.textContent = correctionEnabled ? '开启' : '关闭';
            });
            
            // 添加画布点击事件
            canvas.addEventListener('click', (event) => {
                if (isTracking) {
                    const rect = canvas.getBoundingClientRect();
                    const x = event.clientX - rect.left;
                    const y = event.clientY - rect.top;
                    currentX = x;
                    currentY = y;
                    positionEl.textContent = `X: ${currentX.toFixed(2)}, Y: ${currentY.toFixed(2)}`;
                    drawPoint(currentX, currentY, 'red', 5);
                }
            });
            
            // 开始/停止跟踪
            function toggleTracking() {
                if (!isTracking) {
                    startTracking();
                } else {
                    stopTracking();
                }
            }
            
            // 开始跟踪
            function startTracking() {
                // 检查设备是否支持所需传感器
                if (typeof DeviceMotionEvent === 'undefined' || typeof DeviceOrientationEvent === 'undefined') {
                    statusText.textContent = '错误: 您的设备不支持运动传感器';
                    return;
                }
                
                // 在较新的iOS上，需要请求权限
                if (typeof DeviceMotionEvent.requestPermission === 'function') {
                    DeviceMotionEvent.requestPermission()
                        .then(permissionState => {
                            if (permissionState === 'granted') {
                                initializeTracking();
                            } else {
                                statusText.textContent = '错误: 未授予传感器权限';
                            }
                        })
                        .catch(console.error);
                } else {
                    initializeTracking();
                }
            }
            
            // 初始化跟踪
            function initializeTracking() {
                // 设置起始点为画布中心
                startX = canvas.width / 2;
                startY = canvas.height / 2;
                currentX = startX;
                currentY = startY;
                
                // 清空画布并绘制起始点
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                drawPoint(startX, startY, 'green', 5);
                
                // 添加事件监听器
                window.addEventListener('devicemotion', handleMotion);
                window.addEventListener('deviceorientation', handleOrientation);
                
                isTracking = true;
                startBtn.textContent = '停止跟踪';
                statusText.textContent = '正在跟踪';
                
                // 重置数据
                initialHeading = null;
                stepCount = 0;
                accelSamples = [];
                isWalking = false;
                walkingStatusEl.textContent = '静止';
            }
            
            // 停止跟踪
            function stopTracking() {
                // 移除事件监听器
                window.removeEventListener('devicemotion', handleMotion);
                window.removeEventListener('deviceorientation', handleOrientation);
                
                isTracking = false;
                startBtn.textContent = '开始跟踪';
                statusText.textContent = '已停止';
                
                // 清除行走状态计时器
                if (walkingTimeout) {
                    clearTimeout(walkingTimeout);
                }
            }
            
            // 重置跟踪
            function resetTracking() {
                stopTracking();
                startX = canvas.width / 2;
                startY = canvas.height / 2;
                currentX = startX;
                currentY = startY;
                initialHeading = null;
                stepCount = 0;
                pathHistory = [];
                isWalking = false;
                walkingStatusEl.textContent = '静止';
                
                // 更新显示
                stepCountEl.textContent = stepCount;
                stepLengthEl.textContent = stepLength;
                correctionEl.textContent = correctionEnabled ? '开启' : '关闭';
                orientationEl.textContent = 'α: 0°';
                positionEl.textContent = 'X: 0, Y: 0';
                
                // 清空画布
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                drawPoint(startX, startY, 'green', 5);
            }
            
            // 处理设备运动事件
            function handleMotion(event) {
                if (!isTracking) return;
                
                const accel = event.accelerationIncludingGravity;
                if (!accel) return;
                
                // 使用卡尔曼滤波器平滑加速度数据
                accelData.x = kalmanX.update(accel.x);
                accelData.y = kalmanY.update(accel.y);
                accelData.z = kalmanZ.update(accel.z);
                
                // 更新加速度显示
                accelerationEl.textContent = `X: ${accelData.x.toFixed(2)}, Y: ${accelData.y.toFixed(2)}, Z: ${accelData.z.toFixed(2)}`;
                
                // 保存加速度样本
                const now = performance.now();
                accelSamples.push({
                    magnitude: Math.sqrt(
                        Math.pow(accelData.x, 2) + 
                        Math.pow(accelData.y, 2) + 
                        Math.pow(accelData.z - 9.8, 2)
                    ),
                    timestamp: now
                });
                
                // 限制样本数量
                if (accelSamples.length > 20) {
                    accelSamples.shift();
                }
                
                // 检测步行
                detectStep();
            }
            
            // 处理设备方向事件
            function handleOrientation(event) {
                if (!isTracking) return;
                
                // 保存方向数据
                orientationData.alpha = event.alpha * (Math.PI / 180); // 转换为弧度
                orientationData.beta = event.beta * (Math.PI / 180);
                orientationData.gamma = event.gamma * (Math.PI / 180);
                
                // 计算罗盘方向 (如果设备支持)
                if (typeof event.webkitCompassHeading !== 'undefined') {
                    // iOS设备
                    orientationData.compassHeading = event.webkitCompassHeading * (Math.PI / 180);
                } else if (event.alpha !== null) {
                    // 安卓设备 - 使用alpha角度
                    orientationData.compassHeading = orientationData.alpha;
                }
                
                // 更新方向显示
                const degrees = (orientationData.compassHeading || orientationData.alpha) * (180 / Math.PI);
                orientationEl.textContent = `α: ${degrees.toFixed(1)}°`;
            }
            
            // 检测步行 - 改进的算法
            function detectStep() {
                if (accelSamples.length < 5) return; // 需要足够的样本
                
                // 计算加速度总量
                const accelMagnitude = accelSamples[accelSamples.length - 1].magnitude;
                
                // 计算加速度变化
                const recentSamples = accelSamples.slice(-5);
                const maxMag = Math.max(...recentSamples.map(s => s.magnitude));
                const minMag = Math.min(...recentSamples.map(s => s.magnitude));
                const variation = maxMag - minMag;
                
                // 检测峰值
                const now = performance.now();
                const timeSinceLastPeak = now - lastPeakTime;
                
                // 改进的步行检测 - 需要满足多个条件
                if (accelMagnitude > peakThreshold && 
                    accelMagnitude > lastAccelMagnitude && 
                    timeSinceLastPeak > minTimeBetweenSteps &&
                    variation > minVariationForStep) {
                    
                    // 检测到一步
                    lastPeakTime = now;
                    stepCount++;
                    stepCountEl.textContent = stepCount;
                    
                    // 设置行走状态
                    isWalking = true;
                    walkingStatusEl.textContent = '行走中';
                    
                    // 更新位置
                    updatePosition();
                    
                    // 重置行走状态的计时器
                    if (walkingTimeout) {
                        clearTimeout(walkingTimeout);
                    }
                    walkingTimeout = setTimeout(() => {
                        isWalking = false;
                        walkingStatusEl.textContent = '静止';
                    }, 2000); // 2秒内没有新步伐则认为停止行走
                }
                
                lastAccelMagnitude = accelMagnitude;
            }
            
            // 更新位置
            function updatePosition() {
                if (!isTracking || !isWalking) return; // 只有在行走状态才更新位置
                
                // 获取当前方向
                const currentHeading = orientationData.compassHeading || orientationData.alpha;
                
                // 如果初始方向尚未设置，则设置它
                if (initialHeading === null) {
                    initialHeading = currentHeading;
                }
                
                // 计算相对于初始方向的角度差
                let relativeHeading = currentHeading - initialHeading;
                
                // 规范化角度到 [-π, π]
                while (relativeHeading > Math.PI) relativeHeading -= 2 * Math.PI;
                while (relativeHeading < -Math.PI) relativeHeading += 2 * Math.PI;
                
                // 计算方向向量 - 这是V21的关键部分，保持转向准确
                const directionX = Math.sin(relativeHeading);  // 向右为正
                const directionY = -Math.cos(relativeHeading); // 向上为负
                
                // 上一个位置
                const prevX = currentX;
                const prevY = currentY;
                
                // 计算新位置
                let newX = currentX + directionX * stepLength;
                let newY = currentY + directionY * stepLength;
                
                // 应用轨迹校正 - 这是V22的改进，解决轨迹偏移问题
                if (correctionEnabled && pathHistory.length > 10) {
                    // 计算到中心的向量
                    const toCenterX = startX - newX;
                    const toCenterY = startY - newY;
                    const distanceToCenter = Math.sqrt(toCenterX * toCenterX + toCenterY * toCenterY);
                    
                    // 应用微小的校正力，距离越远校正越强
                    const correctionFactor = 0.005 * (distanceToCenter / 100);
                    newX += toCenterX * correctionFactor;
                    newY += toCenterY * correctionFactor;
                }
                
                // 更新当前位置
                currentX = newX;
                currentY = newY;
                
                // 确保轨迹不超出画布
                currentX = Math.max(0, Math.min(canvas.width, currentX));
                currentY = Math.max(0, Math.min(canvas.height, currentY));
                
                // 保存轨迹点
                pathHistory.push({x: currentX, y: currentY});
                
                // 限制轨迹历史长度
                if (pathHistory.length > 100) {
                    pathHistory.shift();
                }
                
                // 画线
                drawLine(prevX, prevY, currentX, currentY, 'blue', 2);
                
                // 更新位置显示
                positionEl.textContent = `X: ${currentX.toFixed(2)}, Y: ${currentY.toFixed(2)}`;
            }
            
            // 绘制点
            function drawPoint(x, y, color, size) {
                ctx.fillStyle = color;
                ctx.beginPath();
                ctx.arc(x, y, size, 0, Math.PI * 2);
                ctx.fill();
            }
            
            // 绘制线
            function drawLine(x1, y1, x2, y2, color, width) {
                ctx.strokeStyle = color;
                ctx.lineWidth = width;
                ctx.beginPath();
                ctx.moveTo(x1, y1);
                ctx.lineTo(x2, y2);
                ctx.stroke();
            }
            
            // 渲染
            function render() {
                // 清空画布
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                // 如果正在跟踪，绘制起始点
                if (isTracking) {
                    drawPoint(startX, startY, 'green', 5);
                }
            }
        });
    </script>
</body>
</html> 